该内容已被发布者删除 该内容被自由微信恢复
文章于 2022年4月29日 被检测为删除。
查看原文
被用户删除
其他

微信小程序实战:支持登录+支付(附源码)

点击关注 👉 鸭哥聊Java 2022-04-29

大家好,我是鸭哥。


介绍一个可运行的微信小程序登录+支付的demo。接触了小程序简易教程的,想必都知道我们必然有自己的后台应用服务器,来处理我们自己的业务逻辑、请求微信服务完成一定的功能。在此,我们的后台采用java环境,本文将首先介绍环境搭建的过程,随后介绍登录+支付的流程及代码。


一、后台web服务环境搭建


1. 安装jdk、tomcat,ICP备案的域名准备。 


经过icp备案的域名,请自行准备。 


2.配置https,由于小程序请求url必须是https,故而必须配置支持https请求。本人采用的是在阿里云购买的域名,故而采用的证书也是阿里云生成的ssl证书,可参考如下两篇博文进行配置。当然,你也可以通过别的方式生成证书,更或者通过nginx作反向代理到你的服务器。 


同时,务必将您的小程序域名绑定在小程序后端。登入小程序后台,【设置】-【开发设置】-【服务器域名】

 

3. 部署web服务 


如上两步完成后,请务必确认通过你的域名(https://...)可以展示tomcat的默认页之后,开始部署我们的web服务。在此,就简单粗暴的在webapps下建立小程序的根目录,我命名为wechatserver,在此目录下,创建WEB-INFO,下面的目录结构如下: 



classes存放自己写的类的classes文件,lib存放我们项目依赖的jar包,logs用于存放我们的日志输出,web.xml是我们这个项目的配置。 

demo中,我们只有一个servlet接收小程序前端请求,web.xml中增加配置如下:

<servlet> <servlet-name>WechatServlet</servlet-name> <servlet-class>com.icbc.servlet.WechatServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>WechatServlet</servlet-name> <url-pattern>/servlet/WechatServlet</url-pattern>    </servlet-mapping>


4. log4j 应用 


在开发调试中,我们免不了需要通过打印日志进行调试,因此在此增加了日志的使用。web.xml中增加配置:

<context-param> <param-name>log4jConfigLocation</param-name> <param-value>classes/log4j.properties</param-value> </context-param>  


在classes增加文件,log4j.properties,内容如下:

log4j.rootLogger = INFO,toConsole,D,E
log4j.appender.toConsole=org.apache.log4j.ConsoleAppender log4j.appender.toConsole.Target=System.out log4j.appender.toConsole.layout=org.apache.log4j.PatternLayout log4j.appender.toConsole.layout.ConversionPattern=[%d{yyyy-MM-dd HH:mm:ss}] [%p] %m%n
log4j.appender.D = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.D.file = 你的目录/common.loglog4j.appender.D.Append = truelog4j.appender.D.Threshold = info log4j.appender.D.layout = org.apache.log4j.PatternLayoutlog4j.appender.D.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%nlog4j.appender.D.DatePattern='.'yyyy-MM-dd'.log'
log4j.appender.E = org.apache.log4j.DailyRollingFileAppenderlog4j.appender.E.file = 你的目录/error.log log4j.appender.E.Append = truelog4j.appender.E.Threshold = ERROR log4j.appender.E.layout = org.apache.log4j.PatternLayoutlog4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%nlog4j.appender.D.DatePattern='.'yyyy-MM-dd'.log'


log4j 在java中的引用、使用: 
public static Logger logger = Logger.getLogger(WechatServlet.class); 
logger.info(“打印信息”);


二、微信小程序登录+支付


1. 小程序前端目录准备


基于微信小程序工具生成的默认hello world程序,pages下先建立目录order,随后在order目录生成一个新的page,命名为order,结构如下图: 



在index中增加按钮,进入order。 


index.wxml

<view> <navigator class="index-intro__btn btn btn-danger btn-md" url="/pages/order/order">进入商城</navigator> </view>


order.xml中描述商品信息,增加支付按钮,order.js 支付事件处理。


2. 登录+支付 code


流程大概分为几步: 


1)登录,获取code(一个code只能用一次) 


2)通过code获取openid(通过请求服务器,由服务器请求微信获取并返回小程序)。微信登录+获取openid接口。


3)小程序请求服务器进行预下单,上送商品详情、金额、openid。 


4)服务器端接收请求,根据请求订单数据、生成第三方订单号,调用微信的统一下单接口。 


5)服务器收到预下单信息后,签名并组装支付数据,返回给小程序。所需数据见:小程序支付接口 


6)小程序前端发起支付,并支付完成 


7)服务器收到回调。


2.1 登录,获取code。

onLoad: function (options) {
// 登录 wx.login({ success: function (res) { // 发送 res.code 到后台换取 openId, sessionKey, unionId var that = this; if (res.code) { console.log('获取用户登录态success!' + res.code) app.globalData.code = res.code } else { console.log('获取用户登录态失败!' + res.errMsg) } } })   },


2.2 通过code 获取openid(前端)

getOpenId:function(that, code){ console.log(code); let operFlag = "getOpenid"; console.log(operFlag); wx.request({ url: 'https://xxx/wechatserver/servlet/WechatServlet', data: { code:code, operFlag:operFlag }, header: { 'content-type': 'application/json' }, success: function (res) { console.log(res); var openid = res.data.openid; console.log(openid); that.paypay(that, openid); //预下单并支付 }, fail: function (res) { console.log(res.data.errmsg); console.log(res.data.errcode); }, complete:function(res){ } })  },


2.3 服务器端servlet(复写HttpServlet的doGet doPost函数)doPost的代码片段:

//获取操作类型,根据类型执行不同操作 String operFlag = request.getParameter("operFlag"); logger.info("operFlag=" + operFlag); String results = ""; if("getOpenid".equals(operFlag)){ String code = request.getParameter("code"); logger.info("code=" + code);
String url = "https://api.weixin.qq.com/sns/jscode2session?appid=" + appid + "&secret=" + secretKey + "&js_code=" + code + "&grant_type=authorization_code"; logger.info("url=" + url); results = sendGetReq(url);//发送http请求 } logger.info("results = " + results); response.setContentType("application/json;charset=UTF-8"); response.setHeader("catch-control", "no-catch"); PrintWriter out = response.getWriter(); out.write(results); out.flush();        out.close();


2.4 前端上送订单信息、openid请求预下单(在此,为方便,订单信息直接写死在服务器端了),若成功,则根据服务器端返回数据发起支付。

paypay: function (that, openid) { let operFlag = 'pay'; wx.request({ url: 'https://xxx/wechatserver/servlet/WechatServlet', data: { openid: openid, operFlag: operFlag }, header: { 'content-type': 'application/json' }, success: function (res) { console.log(res); wx.requestPayment({ 'timeStamp': res.data.timeStamp, 'nonceStr': res.data.nonceStr, 'package': res.data.package, 'signType': 'MD5', 'paySign': res.data.sign, 'success': function (res) { if (res.errMsg == "requestPayment:ok") { wx.showToast({ title: '支付成功' }) } }, 'fail': function (res) { } }) }, fail: function (res) { console.log(res.data.errmsg); console.log(res.data.errcode); }, complete: function (res) { } })  },


2.4 服务器端预下单,2.5并签名返回支付请求数据。

if("pay".equals(operFlag)){ String openid = request.getParameter("openid"); logger.info("openid = " + openid); String url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; String reqStr = getReqStr(openid); //组装预下单的请求数据 logger.info("reqStr=" + reqStr); results = sendPost(url,reqStr);//发送post数据到微信预下单 logger.info("prepay from weixin: \n " + results); Map<String,String> return_data = null; try { return_data = WXPayUtil.xmlToMap(results);//微信的一个工具类 } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); logger.error(e.getMessage()); } String return_code = return_data.get("return_code"); logger.info("return_code=" + return_code); if("SUCCESS".equals(return_code)){ String prepay_id = return_data.get("prepay_id"); results = conPayParam(prepay_id); //组装返回数据 }else{ results ="{\"return_code\":\"fail\"}";            }        }


三、实战中遇到的问题


预下单和支付请求中,签名的密钥使用的是商户密钥,但是用code获取openid是使用小程序对应的secret key,这个可以在小程序的后台看到。


微信小程序前端发起post请求到服务器端时,服务器端收不到请求参数。原因是:微信API接口wx.request中: 


a) 对于 GET 方法的数据,会将数据转换成 query string(encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)…) 

b1) 对于 POST 方法且 header[‘content-type’] 为 application/json 的数据,会对数据进行 JSON 序列化 

b2) 对于 POST 方法且 header[‘content-type’] 为 application/x-www-form-urlencoded 的数据,会将数据转换成 query string (encodeURIComponent(k)=encodeURIComponent(v)&encodeURIComponent(k)=encodeURIComponent(v)…)


所以,如果post请求,为省去服务器端反序列化的操作时,可使用header[‘content-type’] 为 application/x-www-form-urlencoded 的数据。 


如果部署了servlet后,tomcat重启后,需要等几分钟才能生效(原因是我的机器内存比较小,而tomcat又很占用内存资源),待熟悉tomcat 调优。

来源:https://blog.csdn.net/proteen



程序员技术交流群

有不少同学给鸭哥说,现在进大厂太难了!赚钱太难!因此,鸭哥特意邀请了华为、腾讯、阿里的朋友进群,与大家一起交流经验,一起增长技术。

有兴趣入群的同学,可长按扫描下方二维码,一定要备注:城市+昵称+技术方向,根据格式备注,可更快被通过且邀请进群。

▲长按扫描


近期技术热文

1、一程序员被网恋女友诈骗1.8万,警方调查“女友”后发现竟是...

2、16 条 yyds 的代码规范

3、22岁何同学引爆B站!硬核毕设树莓派星轨拍摄仪,上演理工男的终极浪漫

4、消息幂等(去重)通用解决方案,真顶!

点击下方公众号
回复关键字【666
领取资料


我就知道你会点赞+“在看”

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存